// This file is subject to a 1-clause BSD license.
// Its contents can be found in the enclosed LICENSE file.

// Package owm provides commands to do current weather lookups.
package owm

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"path/filepath"
	"sync"
	"time"

	"notabug.org/mouz/bot/app/util"
	"notabug.org/mouz/bot/irc"
	"notabug.org/mouz/bot/irc/cmd"
	"notabug.org/mouz/bot/plugins"
)

func init() { plugins.Register(&plugin{}) }

// CacheTimeout defines the time after which a cache entry is
// considered stale and it must be re-fetched.
const CacheTimeout = time.Minute * 5

// LookupTimeout defines the timeout after which a service request
// is considered failed.
const LookupTimeout = time.Second * 5

// MaxFetchFrequency defines the maximum number of GET requests per
// minute to the weather server
const MaxFetchFrequency = 60

type plugin struct {
	m                   sync.Mutex
	cmd                 *cmd.Set
	currentWeatherCache map[string]*owmCurrent
	fetchTimeCache      map[time.Time]bool
	config              struct {
		OwmAPIKey string
	}
}

// Load initializes the module and loads any internal resources
// which may be required.
func (p *plugin) Load(prof irc.Profile) error {
	p.currentWeatherCache = make(map[string]*owmCurrent)
	p.fetchTimeCache = make(map[time.Time]bool)

	p.cmd = cmd.New(prof.CommandPrefix(), nil)
	p.cmd.Bind(TextCurrentWeatherName, false, p.cmdCurrentWeather).
		Add(TextLocation, true, cmd.RegAny)

	file := filepath.Join(prof.Root(), "weather.cfg")
	return util.ReadFile(file, &p.config, false)
}

// Unload cleans the module up and unloads any internal resources.
func (p *plugin) Unload(prof irc.Profile) error {
	p.config.OwmAPIKey = ""
	return nil
}

// Dispatch sends the given, incoming IRC message to the plugin for
// processing as it sees fit.
func (p *plugin) Dispatch(w irc.ResponseWriter, r *irc.Request) {
	if len(p.config.OwmAPIKey) > 0 {
		p.cmd.Dispatch(w, r)
	}
}

// fetch fetches the given URL. It returns the content of the
// response, or an error if there were too many fetches in the last
// minute.
func (p *plugin) fetch(serviceURL, query string) ([]byte, error) {

	if p.fetchOverload() {
		return nil, fmt.Errorf("Number of API requests exceeds %d per minute", MaxFetchFrequency)
	}
	p.fetchTimeCache[time.Now()] = true

	url := fmt.Sprintf(serviceURL, TextUnits, TextLanguage, p.config.OwmAPIKey, query)
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	return data, nil
}

// fetchOverload returns true iff too many fetches are done recently
// (in the last minute).
func (p *plugin) fetchOverload() bool {

	// remove old entries from time cache
	isold := make(map[time.Time]bool)
	for t := range p.fetchTimeCache {
		if t.Add(time.Minute).Before(time.Now()) {
			isold[t] = true
		}
	}
	for t := range isold {
		delete(p.fetchTimeCache, t)
	}

	// check remaining length of time cache
	if len(p.fetchTimeCache) >= MaxFetchFrequency {
		return true
	}
	return false
}
